package com.fingard.net.niosocket;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;

import com.fingard.FGBiz;
import com.fingard.text.StringHelper;


public class ServerSocketNio implements Runnable{
	
	private int listenPort = 0;
	private int maxQueueLen;
	
	/**默认字符集*/
	private String defCharset;
	
	/**协议*/
	private String protocol;
	
	private ExecutorService execService;
	private Class<?> classRunnable;
	
	/**
	 * @param p_listenPort 侦听端口
	 * @param p_maxQueueCount - requested maximum length of the queue of incoming connections.
	 * @param p_charset 默认字符集
	 * @param p_protocol 协议，支持http
	 * */
	public ServerSocketNio(int p_listenPort, int p_maxQueueLen,ExecutorService p_exeService, Class<?> p_class, String p_protocol, String p_charset)
    {
		listenPort = p_listenPort;
		if(p_maxQueueLen <= 0){
			maxQueueLen = 500;
		}else{
			maxQueueLen = p_maxQueueLen;
		}
		execService = p_exeService;
		classRunnable = p_class;
		if(StringHelper.isNullOrEmpty(p_protocol)){
			protocol = "";
		}else{
			protocol = p_protocol.toLowerCase();
		}
		if(StringHelper.isNullOrEmpty(p_charset)){
			defCharset = "gbk";
		}else{
			defCharset = p_charset;
		}
    }
	
	public void run() {
		try{
			start();
		}catch(Throwable t){
			FGBiz.limitMsg.exception("ServerSocketNio.run", t);
		}
	}
	private Selector selector = null;
    private ServerSocketChannel serverSocketChannel = null;
    
    private boolean needStop;
    
	public void start(){
		
    	try {
    		needStop = false;
    		long tmpSeqID=0;
			selector = Selector.open();
			serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);//非阻塞
            serverSocketChannel.socket().setReuseAddress(true);
            serverSocketChannel.socket().bind(new InetSocketAddress(listenPort), maxQueueLen);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            while(true){
            	try{
            		if(needStop){
                		break;
                	}
            		if(selector.select() > 0){
                		Iterator<SelectionKey> itk = selector.selectedKeys().iterator();
                		while (itk.hasNext()) {
                			SocketChannel skChannel = null;
                            SelectionKey readyKey = itk.next();
                            
                            itk.remove();//直接移除
                            if(readyKey.isAcceptable()){
                                ServerSocketChannel serverChannel = (ServerSocketChannel)readyKey.channel();
                                skChannel = serverChannel.accept();
                                skChannel.configureBlocking(false);
                                skChannel.socket().setSoTimeout(5000);//5秒
                                NioReceiver nioReceiver;
                                if("http".equals(protocol)){
                                	nioReceiver = new HttpNioReceiver(skChannel, defCharset);
                                }else{
                                	nioReceiver = new SocketNioReceiver(skChannel, defCharset);
                                }
                                nioReceiver.connTime = new Date();
                				tmpSeqID++;
                				nioReceiver.seqIDInner = tmpSeqID;
                				skChannel.register(selector, SelectionKey.OP_READ, nioReceiver);
                                
                            }else if(readyKey.isReadable()){
                            	skChannel = (SocketChannel)readyKey.channel();
                            	NioReceiver nioReceiver =(NioReceiver)readyKey.attachment();

                            	nioReceiver.receiveBytes();
                				if(nioReceiver.isStopRead()){
                					readyKey.cancel();
                		            //String strFull = nioReceiver.getRawReqStr();
                		            //System.out.println("-------\r\n"+strFull+"\r\n-----");
                					try{
                						RunCommand cmd = (RunCommand)classRunnable.newInstance();
            		                	cmd.nioReceiver = nioReceiver;
            		                	execService.execute(cmd);
                					}catch(Exception ex){
                						writeRespDirect(skChannel, ex.getMessage());
                					}
                				}//else{//继续读
                				//	skChannel.register(selector, SelectionKey.OP_READ, nioReceiver);
                				//}
                            }else{
                            	SelectableChannel channel = readyKey.channel();
                            	channel.close();
                            	readyKey.cancel();
                            }
                        }
                	}
            	}catch (Throwable e) {
        			e.printStackTrace();
        		}
            }
		} catch (Throwable e) {
			e.printStackTrace();
		}finally{
			try {
				if(selector!=null){
					selector.close();
				}
            } catch(Exception ex) {
            	ex.printStackTrace();
            }  
            try {
            	if(serverSocketChannel!=null){
            		serverSocketChannel.close();
            	} 
            } catch(Exception ex) {
            	ex.printStackTrace();
            }
		}
	}
	
	public String stop(){
		needStop = true;
		try {
        	if(serverSocketChannel!= null){
        		serverSocketChannel.close();
        	}
        } catch(Exception ex) {
        	FGBiz.limitMsg.exception("ServerSocketNio.stop", ex);
        }
		String retValue = "关闭侦听端口"+listenPort;
		try {
			if(selector!=null){
				selector.close();
			}
        } catch(Exception ex) {
        	FGBiz.limitMsg.exception("ServerSocketNio.stop", ex);
        }
        return retValue;
	}
	
	public void writeRespDirect(SocketChannel skChannel, String resp){
		byte[] respBytes;
		try {
			respBytes = resp.getBytes(this.defCharset);
			ByteBuffer respBf = ByteBuffer.wrap(respBytes);
			skChannel.write(respBf);
			//skChannel.shutdownInput();
			//skChannel.shutdownOutput();
			skChannel.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}
